const { ccclass, property } = cc._decorator;

export default class ResourceManager {
  private static _instance: ResourceManager;
  private constructor() {}

  public static getInstance(): ResourceManager {
    if (!this._instance) {
      this._instance = new ResourceManager();
    }
    return this._instance;
  }
  private loadedBundles: Set<string> = new Set();
  private assetCache: Map<string, cc.Asset> = new Map();

  // ================== Bundle 加载 ==================

  /**
   * 异步加载 bundle（如果尚未加载）
   */
  public async loadBundleAsync(
    bundleName: string
  ): Promise<cc.AssetManager.Bundle> {
    if (this.loadedBundles.has(bundleName)) {
      const existingBundle = cc.assetManager.getBundle(bundleName);
      if (existingBundle) {
        return existingBundle;
      }
    }

    return new Promise((resolve, reject) => {
      cc.assetManager.loadBundle(bundleName, (err, bundle) => {
        if (err) {
          console.error(
            `[ResourceManager] 加载 bundle "${bundleName}" 失败`,
            err
          );
          reject(err);
        } else {
          this.loadedBundles.add(bundleName);
          resolve(bundle);
        }
      });
    });
  }

  // ================== 单个资源加载 ==================

  /**
   * 加载单个资源（支持回调或 Promise）
   */
  public async loadAsset<T extends cc.Asset>(
    bundleName: string,
    path: string,
    type: typeof cc.Asset,
    callback?: (error: Error | null, asset: T | null) => void
  ): Promise<T> {
    try {
      const bundle = await this.loadBundleAsync(bundleName);
      const cacheKey = `${bundleName}/${path}`;
      if (this.assetCache.has(cacheKey)) {
        const cached = this.assetCache.get(cacheKey) as T;
        callback?.(null, cached);
        return cached;
      }

      return new Promise<T>((resolve, reject) => {
        bundle.load(path, type, (err, asset) => {
          if (err) {
            console.error(`[ResourceManager] 加载资源失败: ${path}`, err);
            callback?.(err, null);
            reject(err);
          } else {
            this.assetCache.set(cacheKey, asset as T);
            callback?.(null, asset as T);
            resolve(asset as T);
          }
        });
      });
    } catch (e) {
      callback?.(e as Error, null);
      throw e;
    }
  }

  // ================== 批量加载目录 ==================

  /**
   * 加载某个目录下的所有资源
   */
  public async loadDir<T extends cc.Asset>(
    bundleName: string,
    dirPath: string,
    type: typeof cc.Asset,
    onProgress?: (finished: number, total: number) => void
  ): Promise<T[]> {
    const bundle = await this.loadBundleAsync(bundleName);

    return new Promise<T[]>((resolve, reject) => {
      bundle.loadDir(dirPath, type, onProgress, (err, assets) => {
        if (err) {
          console.error(`[ResourceManager] 加载目录失败: ${dirPath}`, err);
          reject(err);
        } else {
          const assetList = assets as unknown as T[];
          assetList.forEach((asset) => {
            const cacheKey = `${bundleName}/${dirPath}/${asset.name}`;
            this.assetCache.set(cacheKey, asset);
          });
          resolve(assetList);
        }
      });
    });
  }

  // ================== 场景加载 ==================

  /**
   * 异步加载场景
   * 注意：这种方式适用于从 resources 目录加载场景，不是从 bundle 加载。
   * 如果要从 bundle 加载场景，需要先加载 bundle 再调用 bundle.load()。
   */
  public async preloadSceneWithProgress(
    sceneName: string,
    onProgress?: (finished: number, total: number) => void
  ): Promise<void> {
    return new Promise((resolve, reject) => {
      cc.loader.loadRes(
        sceneName,
        cc.SceneAsset,
        (completedCount: number, totalCount: number, item: any) => {
          onProgress?.(completedCount, totalCount);
        },
        (err: Error | null, asset: cc.SceneAsset | null) => {
          if (err) {
            reject(err);
          } else {
            cc.director.runScene(asset.scene);
            resolve();
          }
        }
      );
    });
  }

  // ================== 资源释放 ==================

  /**
   * 释放指定资源
   */
  public releaseAsset(asset: cc.Asset): void {
    if (!asset) return;
    cc.assetManager.releaseAsset(asset);
    this.assetCache.forEach((value, key) => {
      if (value === asset) {
        this.assetCache.delete(key);
      }
    });
  }

  /**
   * 释放整个 bundle 的资源
   */
  public releaseBundle(bundleName: string): void {
    const bundle = cc.assetManager.getBundle(bundleName);
    if (bundle) {
      bundle.releaseAll();
    }
    this.loadedBundles.delete(bundleName);
    this.assetCache.forEach((_, key) => {
      if (key.startsWith(`${bundleName}/`)) {
        this.assetCache.delete(key);
      }
    });
  }
}
